/*
  LCDHC595.cpp - Base class that provides LCD Interface With Arduino
 
  LCDHC595 : Library Interface for Arduino 
  -> Used Shift Register Serial to Parallel(74HC595) Interface LCD
  -> Interface 4 Bit Mode
  -> Output 74HC595 Pin : Q7 Q6 Q5 Q4 Q3 Q2 Q1 Q0
  -> LCD Character Pin  : D7 D6 D5 D4 EN RW RS BL
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.
 
 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.
 
 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
 Create : 12 November 2008 by Eakachai Makarn.(ETT CO.,LTD. : WWW.ETT.CO.TH)
*/

#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "wiring.h"
#include "LCDHC595.h"

// Bit Masks For Control LCD Signal Interface
// SPI Format : MSB First
// Output     : Q7,Q6,Q5,Q4,Q3,Q2,Q1,Q0
// LCD        : D7,D6,D5,D4,EN,RW,RS,BL
#define 	SHIFT_BL 			0x01    	// Q0 = Backlight(0=FF,1=ON)
#define 	SHIFT_RS 			0x02    	// Q1 = RS(0=Command,1=Data)
#define 	SHIFT_RW 			0x04    	// Q2 = RW(0=Write,1=Read)
#define 	SHIFT_EN 			0x08    	// Q3 = EN(0=Disable,1=Enable)

//Data, Clock and Strobe pins are set using the constructor
int dat_pin;
int str_pin;
int clk_pin;

// Function Interface
LCDHC595::LCDHC595 (int _dat_pin, int _str_pin, int _clk_pin) 
{
  dat_pin = _dat_pin;
  str_pin = _str_pin;
  clk_pin = _clk_pin;
}

// Initial Character LCD Display
void LCDHC595::Initial() 
{
  pinMode(dat_pin,OUTPUT);				// Initial Signal Interface
  pinMode(str_pin,OUTPUT);
  pinMode(clk_pin,OUTPUT);

  digitalWrite(str_pin,LOW); 				// Standby Latch
  digitalWrite(clk_pin,LOW); 				// Standby Clock
  digitalWrite(dat_pin,LOW); 				// Standby Data
  delay(50);						// Power-On Delay
  
  // Start of 4 Bit LCD Interface Initial
  _Shift_Nibble(0x30, true);				// Step[1] = D7:D6:D4:D5 = 0:0:1:1
  delay(5);						      // Wait 4.1mS
  _Shift_Nibble(0x30, true);  			// Step[2] = D7:D6:D4:D5 = 0:0:1:1
  delayMicroseconds(100);				// Wait 100uS
  _Shift_Nibble(0x30, true);  			// Step[3] = D7:D6:D4:D5 = 0:0:1:1
  _Busy_LCD(true);					// Wait Busy Command
  _Shift_Nibble(0x20, true);  			// Step[4] = D7:D6:D4:D5 = 0:0:1:0
  _Busy_LCD(true);					// Wait Busy Command
  // End of 4 Bit LCD Interface Initial

  Command(0x28);  					// Function Set (DL=0 4-Bit,N=1 2 Line,F=0 5X7)
  Command(0x0C);  					// Display on/off Control (Entry Display,Cursor off,Cursor not Blink)
  Command(0x06);  					// Entry Mode Set (I/D=1 Increment,S=0 Cursor Shift)
  Command(0x01);  					// Clear Display  (Clear Display,Set DD RAM Address=0)
}

// Write Command to LCD
void LCDHC595::Command(int value) 
{
  _Shift_Byte(value, true);				// Command
  _Busy_LCD(true);					// Wait Busy Command
}

// Print Value of Unsigned Char
void LCDHC595::Print(uint8_t b)
{
  _Shift_Byte(b, false);				// Write Data to LCD
  _Busy_LCD(false);					// Wait Busy Data
}

//Print Value of Character
void LCDHC595::Print(char c)
{
  Print((byte) c);
}

// Print Value of Constant
void LCDHC595::Print(const char c[])
{
  while (*c)
  {
    Print(*c++);
  }
}

// Print Value of Singed Integer
void LCDHC595::Print(int n)
{
  Print((long) n);
}

// Print Value of Unsigned Integer
void LCDHC595::Print(unsigned int n)
{
  Print((unsigned long) n);
}

// Print Value of Long
void LCDHC595::Print(long n)
{
  if (n < 0) 
  {
    Print('-');
    n = -n;
  }
  _printNumber(n, 10);
}

// Print Value of Unsigned Long
void LCDHC595::Print(unsigned long n)
{
  _printNumber(n, 10);
}

// Print Value of Long with Format(BYTE,BCD,BIN,OCT,HEX,DEC)
void LCDHC595::Print(long n, int base)
{
  if (base == BYTE)					//BYTE
  {
    Print((char) n);
  }
  else if (base == BCD)					//BCD
  {
    if (n < 0x10)
    {
      Print("0");
    }  
    Print(n, HEX);
  }
  else if (base == DEC)					//DEC
  {
    Print(n);
  }
  else							//BIN,OCT,HEX
  {
    _printNumber(n, base);
  }
}

// Set Cursor Position
void LCDHC595::SetCursor(int Cursor)
{
  Command(Cursor | 0x80);				// Set DDRAM Command = D7 Set
}

// Clear Screen Display
void LCDHC595::ClearScreen()
{
  Command(lcd_clear_screen);				// Clear Screen Display
}

// Clear LCD From Cursor with Length
void LCDHC595::ClearLCD(int Cursor, int length)
{
  SetCursor(Cursor);					// Set Cursor for Start Clear Display
  for(int i=0; i<length; i++)
  {
    Print(0x20,BYTE);  					// Write Space for Clear Display
  }
  SetCursor(Cursor);					// Set Cursor For Start New Display
}

// Backlight LED ON/OFF Control
void LCDHC595::Backlight(bool LED_Status)		// Backlight ON/OFF	
{
  _Backlight_LED = LED_Status;
}

// Read Busy LCD Status
void LCDHC595::_Busy_LCD(bool lcd_command)
{ 
  if (!lcd_command) 
  {
    delayMicroseconds(100);				// Delay Data = 100uS
  }
  else
  { 
    delay(2);						// Delay Command = 2mS
  }
}

// Shift Byte to LCD
void LCDHC595::_Shift_Byte(int value, bool lcd_command) 
{
  int nibble = 0;

  nibble = value & 0xF0;     				// Send LCD Data MSB 4 Bit
  _Shift_Nibble(nibble, lcd_command);

  nibble = (value << 4) & 0xF0; 			// Send LCD Data LSB 4 Bit 
  _Shift_Nibble(nibble, lcd_command);  
}

// Send 4 Bit Data to LCD
void LCDHC595::_Shift_Nibble(int nibble, bool lcd_command) 
{
  if(_Backlight_LED)
  {
    nibble |=  SHIFT_BL; 				// BL = HIGH(On Backlight)
  }
  else
  {
    nibble &= ~SHIFT_BL; 				// BL = LOW(OFF Backlight)
  }

  if (lcd_command) 
  {
    nibble &= ~SHIFT_RS; 				// RS = LOW(Command)    
  }
  else
  {    
    nibble |=  SHIFT_RS; 				// RS = HIGH(Data)     
  }
  
  nibble &= ~SHIFT_RW; 				      // RW = LOW(Write)

  nibble &= ~SHIFT_EN; 					// Enable = LOW(Standby)
  _Shift_HC595(nibble);

  nibble |= SHIFT_EN; 					// Enable = HIGH(Start Enable)
  _Shift_HC595(nibble);

  nibble &= ~SHIFT_EN; 					// Enable = LOW(Standby)
  _Shift_HC595(nibble); 
}

// Strobe Signal to LCD
void LCDHC595::_Shift_HC595(int value) 
{
  shiftOut(dat_pin, clk_pin, MSBFIRST, value);	// Shift Byte to 74HC595

  digitalWrite(str_pin, HIGH);			// Active Latch
  delayMicroseconds(100);
  digitalWrite(str_pin, LOW);				// Standby Latch
}

// Print Value of Unsigned Long with Format(DEC,HEX,OCT,BIN,BYTE)
void LCDHC595::_printNumber(unsigned long n, uint8_t base)
{
  unsigned char buf[8 * sizeof(long)]; 		// Assumes 8-bit chars. 
  unsigned long i = 0;

  if (n == 0) 						
  {
    Print('0');
    return;
  } 

  while (n > 0) 
  {
    buf[i++] = n % base;
    n /= base;
  }

  for (; i > 0; i--)
  {
    Print((char) (buf[i - 1] < 10 ?
                  '0' + buf[i - 1] :		// 0..9
                  'A' + buf[i - 1] - 10));	// A..F
  }
}
